สำรวจเทคนิค React ref forwarding ขั้นสูงเพื่อสร้าง Component API ที่ยืดหยุ่นและดูแลรักษาง่าย เรียนรู้รูปแบบการใช้งานจริงเพื่อสร้าง UI elements ที่นำกลับมาใช้ใหม่ได้และ custom input components
รูปแบบการทำ Ref Forwarding ใน React: การออกแบบ Component API อย่างมืออาชีพ
Ref forwarding เป็นเทคนิคที่ทรงพลังใน React ที่ช่วยให้คุณสามารถส่ง ref ผ่าน component ไปยัง child ของมันได้โดยอัตโนมัติ สิ่งนี้ทำให้ parent component สามารถโต้ตอบโดยตรงกับ DOM element หรือ instance ของ component ที่เฉพาะเจาะจงภายใน child ได้ แม้ว่า child เหล่านั้นจะซ้อนกันอยู่ลึกแค่ไหนก็ตาม การทำความเข้าใจและใช้ ref forwarding อย่างมีประสิทธิภาพนั้นสำคัญอย่างยิ่งต่อการสร้าง Component API ที่ยืดหยุ่น นำกลับมาใช้ใหม่ได้ และดูแลรักษาง่าย
ทำไม Ref Forwarding จึงสำคัญต่อการออกแบบ Component API
เมื่อออกแบบ React component โดยเฉพาะอย่างยิ่งที่ตั้งใจจะให้นำกลับมาใช้ใหม่ได้ สิ่งสำคัญคือต้องพิจารณาว่านักพัฒนาคนอื่นจะโต้ตอบกับมันอย่างไร Component API ที่ออกแบบมาอย่างดีควรมีลักษณะดังนี้:
- ใช้งานง่าย: ง่ายต่อการทำความเข้าใจและใช้งาน
- ยืดหยุ่น: สามารถปรับให้เข้ากับกรณีการใช้งานที่แตกต่างกันได้โดยไม่ต้องแก้ไขมากมาย
- ดูแลรักษาง่าย: การเปลี่ยนแปลงการทำงานภายในของ component ไม่ควรทำให้โค้ดภายนอกที่ใช้งานมันพัง
Ref forwarding มีบทบาทสำคัญในการบรรลุเป้าหมายเหล่านี้ มันช่วยให้คุณสามารถเปิดเผยส่วนโครงสร้างภายในของ component ของคุณสู่โลกภายนอกได้ ในขณะที่ยังคงควบคุมการทำงานภายในของ component ได้
พื้นฐานของ React.forwardRef
หัวใจหลักของ ref forwarding ใน React คือ Higher-Order Component (HOC) ที่ชื่อว่า `React.forwardRef` ฟังก์ชันนี้จะรับ rendering function เป็นอาร์กิวเมนต์ และคืนค่าเป็น React component ใหม่ที่สามารถรับ `ref` prop ได้
นี่คือตัวอย่างง่ายๆ:
import React, { forwardRef } from 'react';
const MyInput = forwardRef((props, ref) => {
return ;
});
export default MyInput;
ในตัวอย่างนี้ `MyInput` เป็น functional component ที่ใช้ `forwardRef` โดย `ref` prop ที่ส่งมายัง `MyInput` จะถูกกำหนดให้กับ element `input` โดยตรง ซึ่งช่วยให้ parent component สามารถเข้าถึง DOM node จริงๆ ของ input field ได้
การใช้ Ref ที่ถูกส่งต่อ
นี่คือวิธีที่คุณอาจใช้ `MyInput` component ใน parent component:
import React, { useRef, useEffect } from 'react';
import MyInput from './MyInput';
const ParentComponent = () => {
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
);
};
export default ParentComponent;
ในตัวอย่างนี้ `ParentComponent` สร้าง ref โดยใช้ `useRef` และส่งต่อไปยัง `MyInput` component จากนั้น `useEffect` hook จะใช้ ref เพื่อโฟกัสไปที่ input field เมื่อ component ถูก mount ซึ่งแสดงให้เห็นว่า parent component สามารถจัดการ DOM element ภายใน child component ได้โดยตรงผ่านการใช้ ref forwarding
รูปแบบ Ref Forwarding ที่พบบ่อยสำหรับการออกแบบ Component API
ตอนนี้ เรามาสำรวจรูปแบบ ref forwarding ที่พบบ่อยและมีประโยชน์ ซึ่งจะช่วยปรับปรุงการออกแบบ Component API ของคุณได้อย่างมาก
1. การส่งต่อ Refs ไปยัง DOM Elements
ดังที่แสดงในตัวอย่างพื้นฐานข้างต้น การส่งต่อ ref ไปยัง DOM element เป็นรูปแบบพื้นฐานที่สำคัญ สิ่งนี้ช่วยให้ parent component สามารถเข้าถึงและจัดการ DOM node ที่เฉพาะเจาะจงภายใน component ของคุณได้ ซึ่งมีประโยชน์อย่างยิ่งสำหรับ:
- การจัดการโฟกัส: การตั้งค่าโฟกัสบน input field หรือ element ที่โต้ตอบได้อื่นๆ
- การวัดขนาดของ element: การรับค่าความกว้างหรือความสูงของ element
- การเข้าถึงคุณสมบัติของ element: การอ่านหรือแก้ไข attribute ของ element
ตัวอย่าง: Component ปุ่มที่ปรับแต่งได้
พิจารณา component ปุ่มที่อนุญาตให้ผู้ใช้ปรับแต่งลักษณะภายนอกได้
import React, { forwardRef } from 'react';
const CustomButton = forwardRef((props, ref) => {
const { children, ...rest } = props;
return (
);
});
export default CustomButton;
ตอนนี้ parent component สามารถรับการอ้างอิงไปยัง element ของปุ่ม และดำเนินการต่างๆ เช่น การคลิกปุ่มผ่านโปรแกรม หรือเปลี่ยนสไตล์ของมันได้
2. การส่งต่อ Refs ไปยัง Child Components
Ref forwarding ไม่ได้จำกัดอยู่แค่ DOM element เท่านั้น คุณยังสามารถส่งต่อ ref ไปยัง React component อื่นๆ ได้อีกด้วย สิ่งนี้ช่วยให้ parent component สามารถเข้าถึง instance methods หรือ properties ของ child component ได้
ตัวอย่าง: Component อินพุตแบบควบคุม (Controlled Input)
สมมติว่าคุณมี custom input component ที่จัดการ state ของตัวเอง คุณอาจต้องการเปิดเผย method เพื่อล้างค่าอินพุตผ่านโปรแกรม
import React, { useState, forwardRef, useImperativeHandle } from 'react';
const ControlledInput = forwardRef((props, ref) => {
const [value, setValue] = useState('');
const clearInput = () => {
setValue('');
};
useImperativeHandle(ref, () => ({
clear: clearInput,
}));
return (
setValue(e.target.value)}
/>
);
});
export default ControlledInput;
ในตัวอย่างนี้ `useImperativeHandle` ถูกใช้เพื่อเปิดเผย method `clear` ให้กับ parent component จากนั้น parent สามารถเรียกใช้ method นี้เพื่อล้างค่าในอินพุตได้
import React, { useRef } from 'react';
import ControlledInput from './ControlledInput';
const ParentComponent = () => {
const inputRef = useRef(null);
const handleClearClick = () => {
if (inputRef.current) {
inputRef.current.clear();
}
};
return (
);
};
export default ParentComponent;
รูปแบบนี้มีประโยชน์เมื่อคุณต้องการเปิดเผยฟังก์ชันการทำงานเฉพาะของ child component ให้กับ parent ของมัน ในขณะที่ยังคงควบคุม state ภายในของ child ได้
3. การรวม Refs สำหรับ Component ที่ซับซ้อน
ใน component ที่ซับซ้อนมากขึ้น คุณอาจจำเป็นต้องส่งต่อ ref หลายตัวไปยัง element หรือ component ที่แตกต่างกันภายใน component ของคุณ ซึ่งสามารถทำได้โดยการรวม ref โดยใช้ฟังก์ชันที่กำหนดเอง
ตัวอย่าง: Component แบบผสมที่มี Element ที่โฟกัสได้หลายตัว
สมมติว่าคุณมี component ที่มีทั้ง input field และปุ่ม คุณต้องการให้ parent component สามารถโฟกัสได้ทั้ง input field หรือปุ่ม
import React, { useRef, forwardRef, useEffect } from 'react';
const CompositeComponent = forwardRef((props, ref) => {
const inputRef = useRef(null);
const buttonRef = useRef(null);
useEffect(() => {
if (typeof ref === 'function') {
ref({
input: inputRef.current,
button: buttonRef.current,
});
} else if (ref && typeof ref === 'object') {
ref.current = {
input: inputRef.current,
button: buttonRef.current,
};
}
}, [ref]);
return (
);
});
export default CompositeComponent;
ในตัวอย่างนี้ `CompositeComponent` ใช้ ref ภายในสองตัวคือ `inputRef` และ `buttonRef` จากนั้น `useEffect` hook จะรวม ref เหล่านี้เป็น object เดียวและกำหนดให้กับ ref ที่ถูกส่งต่อมา ซึ่งช่วยให้ parent component สามารถเข้าถึงได้ทั้ง input field และปุ่ม
import React, { useRef } from 'react';
import CompositeComponent from './CompositeComponent';
const ParentComponent = () => {
const compositeRef = useRef(null);
const handleFocusInput = () => {
if (compositeRef.current && compositeRef.current.input) {
compositeRef.current.input.focus();
}
};
const handleFocusButton = () => {
if (compositeRef.current && compositeRef.current.button) {
compositeRef.current.button.focus();
}
};
return (
);
};
export default ParentComponent;
รูปแบบนี้มีประโยชน์เมื่อคุณต้องการเปิดเผย element หรือ component หลายตัวภายใน component ที่ซับซ้อนให้กับ parent component
4. การส่งต่อ Ref แบบมีเงื่อนไข
บางครั้งคุณอาจต้องการส่งต่อ ref ภายใต้เงื่อนไขบางอย่างเท่านั้น สิ่งนี้มีประโยชน์เมื่อคุณต้องการให้มีพฤติกรรมเริ่มต้น แต่ก็อนุญาตให้ parent component สามารถ override ได้
ตัวอย่าง: Component ที่มี Input Field ที่เลือกได้ (Optional)
สมมติว่าคุณมี component ที่จะ render input field ก็ต่อเมื่อมีการตั้งค่า prop บางอย่างเท่านั้น คุณต้องการส่งต่อ ref ก็ต่อเมื่อ input field ถูก render จริงๆ
import React, { forwardRef } from 'react';
const ConditionalInput = forwardRef((props, ref) => {
const { showInput, ...rest } = props;
if (showInput) {
return ;
} else {
return No input field;
}
});
export default ConditionalInput;
ในตัวอย่างนี้ ref จะถูกส่งต่อไปยัง element `input` ก็ต่อเมื่อ prop `showInput` เป็น true เท่านั้น มิฉะนั้น ref จะถูกละเว้น
5. การส่งต่อ Ref กับ Higher-Order Components (HOCs)
เมื่อใช้ Higher-Order Components (HOCs) สิ่งสำคัญคือต้องแน่ใจว่า ref ถูกส่งต่อไปยัง component ที่ถูกครอบ (wrapped component) อย่างถูกต้อง หากคุณไม่จัดการ ref อย่างถูกต้อง parent component อาจไม่สามารถเข้าถึง component ที่อยู่ข้างใต้ได้
ตัวอย่าง: HOC ง่ายๆ สำหรับการเพิ่มเส้นขอบ
import React, { forwardRef } from 'react';
const withBorder = (WrappedComponent) => {
const WithBorder = forwardRef((props, ref) => {
return (
);
});
WithBorder.displayName = `withBorder(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return WithBorder;
};
export default withBorder;
ในตัวอย่างนี้ `withBorder` HOC ใช้ `forwardRef` เพื่อให้แน่ใจว่า ref ถูกส่งต่อไปยัง component ที่ถูกครอบ นอกจากนี้ยังมีการตั้งค่า property `displayName` เพื่อให้การดีบักง่ายขึ้น
ข้อควรจำที่สำคัญ: เมื่อใช้ class component กับ HOCs และ ref forwarding, ref จะถูกส่งเป็น prop ปกติไปยัง class component คุณจะต้องเข้าถึงมันโดยใช้ `this.props.ref`
แนวทางปฏิบัติที่ดีที่สุดสำหรับการทำ Ref Forwarding
เพื่อให้แน่ใจว่าคุณใช้ ref forwarding อย่างมีประสิทธิภาพ ให้พิจารณาแนวทางปฏิบัติที่ดีที่สุดต่อไปนี้:
- ใช้ `React.forwardRef` สำหรับ component ที่ต้องการส่งต่อ ref นี่เป็นวิธีมาตรฐานในการเปิดใช้งาน ref forwarding ใน React
- จัดทำเอกสาร Component API ของคุณให้ชัดเจน อธิบายว่า element หรือ component ใดบ้างที่สามารถเข้าถึงได้ผ่าน ref และวิธีใช้งาน
- คำนึงถึงประสิทธิภาพ หลีกเลี่ยงการส่งต่อ ref ที่ไม่จำเป็น เพราะอาจเพิ่มภาระการทำงานได้
- ใช้ `useImperativeHandle` เพื่อเปิดเผย method หรือ property ที่จำกัด สิ่งนี้ช่วยให้คุณควบคุมสิ่งที่ parent component สามารถเข้าถึงได้
- หลีกเลี่ยงการใช้ ref forwarding มากเกินไป ในหลายกรณี การใช้ props เพื่อสื่อสารระหว่าง component จะเป็นทางเลือกที่ดีกว่า
ข้อควรพิจารณาด้านการเข้าถึง (Accessibility)
เมื่อใช้ ref forwarding สิ่งสำคัญคือต้องพิจารณาถึงการเข้าถึงได้ ตรวจสอบให้แน่ใจว่า component ของคุณยังคงเข้าถึงได้สำหรับผู้ใช้ที่มีความพิการ แม้ว่าจะมีการใช้ ref เพื่อจัดการ DOM element ก็ตาม นี่คือเคล็ดลับบางประการ:
- ใช้ ARIA attributes เพื่อให้ข้อมูลเชิงความหมาย สิ่งนี้ช่วยให้เทคโนโลยีสิ่งอำนวยความสะดวกเข้าใจวัตถุประสงค์ของ component ของคุณ
- จัดการโฟกัสให้ถูกต้อง ตรวจสอบให้แน่ใจว่าโฟกัสสามารถมองเห็นได้และคาดเดาได้เสมอ
- ทดสอบ component ของคุณด้วยเทคโนโลยีสิ่งอำนวยความสะดวก นี่เป็นวิธีที่ดีที่สุดในการระบุและแก้ไขปัญหาการเข้าถึงได้
การทำให้เป็นสากล (Internationalization) และการปรับให้เข้ากับท้องถิ่น (Localization)
เมื่อออกแบบ Component API สำหรับผู้ชมทั่วโลก ให้พิจารณาถึงการทำให้เป็นสากล (i18n) และการปรับให้เข้ากับท้องถิ่น (l10n) ตรวจสอบให้แน่ใจว่า component ของคุณสามารถแปลเป็นภาษาต่างๆ และปรับให้เข้ากับบริบททางวัฒนธรรมที่แตกต่างกันได้อย่างง่ายดาย นี่คือเคล็ดลับบางประการ:
- ใช้ไลบรารีสำหรับ i18n และ l10n มีไลบรารีที่ยอดเยี่ยมมากมาย เช่น `react-intl` และ `i18next`
- แยกข้อความทั้งหมดออกไปไว้ภายนอก อย่าเขียนข้อความแบบ hardcode ใน component ของคุณ
- รองรับรูปแบบวันที่และตัวเลขที่แตกต่างกัน ปรับ component ของคุณให้เข้ากับ locale ของผู้ใช้
- พิจารณาเลย์เอาต์แบบขวาไปซ้าย (RTL) บางภาษา เช่น ภาษาอาหรับและฮีบรู จะเขียนจากขวาไปซ้าย
ตัวอย่างจากทั่วโลก
เรามาดูตัวอย่างการใช้ ref forwarding ในบริบทต่างๆ ทั่วโลกกัน:
- ในแอปพลิเคชันอีคอมเมิร์ซ: สามารถใช้ Ref forwarding เพื่อโฟกัสไปที่ช่องค้นหาเมื่อผู้ใช้ไปที่หน้าค้นหา ซึ่งช่วยปรับปรุงประสบการณ์ผู้ใช้สำหรับนักช้อปทั่วโลก
- ในไลบรารีการแสดงข้อมูลด้วยภาพ (data visualization): สามารถใช้ Ref forwarding เพื่อเข้าถึง DOM element ของแผนภูมิและกราฟ ซึ่งช่วยให้นักพัฒนาสามารถปรับแต่งลักษณะและการทำงานตามมาตรฐานข้อมูลของแต่ละภูมิภาคได้
- ในไลบรารีฟอร์ม: สามารถใช้ Ref forwarding เพื่อควบคุมช่องอินพุตผ่านโปรแกรม เช่น การล้างค่าหรือการตรวจสอบความถูกต้อง ซึ่งมีประโยชน์อย่างยิ่งในแอปพลิเคชันที่ต้องปฏิบัติตามกฎระเบียบด้านความเป็นส่วนตัวของข้อมูลที่แตกต่างกันในแต่ละประเทศ
บทสรุป
Ref forwarding เป็นเครื่องมือที่ทรงพลังสำหรับการออกแบบ React Component API ที่ยืดหยุ่นและดูแลรักษาง่าย ด้วยการทำความเข้าใจและใช้รูปแบบที่กล่าวถึงในบทความนี้ คุณสามารถสร้าง component ที่ใช้งานง่าย ปรับให้เข้ากับกรณีการใช้งานต่างๆ และทนทานต่อการเปลี่ยนแปลงได้ อย่าลืมพิจารณาถึงการเข้าถึงได้และการทำให้เป็นสากลเมื่อออกแบบ component ของคุณเพื่อให้แน่ใจว่าผู้ใช้ทั่วโลกสามารถใช้งานได้
ด้วยการเรียนรู้ ref forwarding และเทคนิคขั้นสูงอื่นๆ ของ React อย่างเชี่ยวชาญ คุณจะสามารถเป็นนักพัฒนา React ที่มีประสิทธิภาพและมีคุณค่ามากยิ่งขึ้น จงสำรวจ ทดลอง และขัดเกลาทักษะของคุณต่อไปเพื่อสร้างส่วนติดต่อผู้ใช้ที่น่าทึ่งซึ่งสร้างความสุขให้กับผู้ใช้ทั่วโลก